/*******************************************************************************
 * Copyright (c) 2009 CollabNet.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     CollabNet - initial API and implementation
 ******************************************************************************/
package com.collabnet.subversion.merge.wizards;

import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.CompareViewerSwitchingPane;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.team.core.TeamException;
import org.tigris.subversion.subclipse.core.ISVNLocalResource;
import org.tigris.subversion.subclipse.core.ISVNRemoteFile;
import org.tigris.subversion.subclipse.core.ISVNRemoteFolder;
import org.tigris.subversion.subclipse.core.ISVNRemoteResource;
import org.tigris.subversion.subclipse.core.ISVNRepositoryLocation;
import org.tigris.subversion.subclipse.core.Policy;
import org.tigris.subversion.subclipse.core.SVNException;
import org.tigris.subversion.subclipse.core.SVNProviderPlugin;
import org.tigris.subversion.subclipse.core.commands.ISVNCommand;
import org.tigris.subversion.subclipse.core.history.AliasManager;
import org.tigris.subversion.subclipse.core.history.ILogEntry;
import org.tigris.subversion.subclipse.core.history.LogEntry;
import org.tigris.subversion.subclipse.core.history.LogEntryChangePath;
import org.tigris.subversion.subclipse.core.history.Tags;
import org.tigris.subversion.subclipse.core.resources.RemoteFile;
import org.tigris.subversion.subclipse.core.resources.SVNWorkspaceRoot;
import org.tigris.subversion.subclipse.core.util.SVNUrlWithPegRevision;
import org.tigris.subversion.subclipse.ui.ISVNUIConstants;
import org.tigris.subversion.subclipse.ui.SVNUIPlugin;
import org.tigris.subversion.subclipse.ui.actions.GenerateChangeLogAction;
import org.tigris.subversion.subclipse.ui.compare.ResourceEditionNode;
import org.tigris.subversion.subclipse.ui.compare.SVNCompareEditorInput;
import org.tigris.subversion.subclipse.ui.history.ChangePathsTreeViewer;
import org.tigris.subversion.subclipse.ui.history.HistoryFolder;
import org.tigris.subversion.subclipse.ui.history.HistorySearchViewerFilter;
import org.tigris.subversion.subclipse.ui.history.HistoryTableProvider;
import org.tigris.subversion.svnclientadapter.ISVNClientAdapter;
import org.tigris.subversion.svnclientadapter.ISVNLogMessage;
import org.tigris.subversion.svnclientadapter.ISVNLogMessageChangePath;
import org.tigris.subversion.svnclientadapter.ISVNMergeinfoLogKind;
import org.tigris.subversion.svnclientadapter.SVNRevision;
import org.tigris.subversion.svnclientadapter.SVNRevisionRange;
import org.tigris.subversion.svnclientadapter.SVNUrl;

import com.collabnet.subversion.merge.Activator;
import com.collabnet.subversion.merge.Messages;
import com.collabnet.subversion.merge.dialogs.FilterRevisionsDialog;

public class MergeWizardRevisionsPage extends WizardPage {
	private MergeWizardStandardPage standardPage;
	private String fromUrl;
	
	private IDialogSettings settings;
	private SashForm horizontalSash;
    private SashForm verticalSash;
	private HistoryTableProvider historyTableProvider;
	private ChangePathsTreeViewer changePathsViewer;
	private TableViewer tableHistoryViewer;
	private TextViewer textViewer;
	private Button showCompareButton;
	private CompareViewerSwitchingPane compareViewerPane;
	private ILogEntry[] entries;
	private IResource[] resources;
	private ISVNLocalResource svnResource;
	private ISVNRepositoryLocation repositoryLocation;
	private AliasManager tagManager;
	private ISVNRemoteResource remoteResource;
	private String message;
	private String pageName;
	private boolean pageShown;
	private GenerateChangeLogAction generateChangeLogAction;
	private Button generateChangeLogButton;
	private Text filterText;
	private boolean noEligibleEntries;
	
	private Button moreOptionsButton;
	private FilterRevisionsDialog dialog;
	private HistorySearchViewerFilter historySearchFilter;

	private SVNCompareEditorInput compareInput;
	private boolean showCompare;
	private Map<String, SVNCompareEditorInput> compareInputMap = new HashMap<String, SVNCompareEditorInput>();
	
	private Map<SVNRevision.Number,List<IResource>> revisionToResource = new HashMap<SVNRevision.Number,List<IResource>>();

	private Set<SVNRevision.Number> selectedRevisions = new HashSet<SVNRevision.Number>();
	
	private final static String ALL_AUTHORS = Messages.MergeWizardRevisionsPage_0;
	
	public MergeWizardRevisionsPage(String pageName, String title, ImageDescriptor titleImage, MergeWizardStandardPage standardPage, String message) {
		super(pageName, title, titleImage);
		this.standardPage = standardPage;
		this.message = message;
		this.pageName = pageName;
		settings = Activator.getDefault().getDialogSettings();
	}
	
	public MergeWizardRevisionsPage(String pageName, String title, ImageDescriptor titleImage, MergeWizardStandardPage standardPage) {
		this(pageName, title, titleImage, standardPage, null);
	}	

	public void createControl(Composite parent) {
		final MergeWizard wizard = (MergeWizard)getWizard();
		
		resources = wizard.getResources();
		if (resources != null && resources.length > 0) {
			svnResource = SVNWorkspaceRoot.getSVNResourceFor(resources[0]);
			try {
				repositoryLocation = svnResource.getRepository();
	        } catch (Exception e1) {}
		}
		
		Composite composite = new Composite(parent, SWT.NULL);
		GridLayout layout = new GridLayout();
		layout.verticalSpacing = 0;
		layout.marginHeight = 0;
		composite.setLayout(layout);
		GridData data = new GridData(GridData.FILL_BOTH);
		composite.setLayoutData(data);
		
        horizontalSash = new SashForm(composite, SWT.HORIZONTAL);
        horizontalSash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		
        verticalSash = new SashForm(horizontalSash, SWT.VERTICAL);
		GridLayout sashLayout = new GridLayout();
		sashLayout.verticalSpacing = 0; 
		sashLayout.marginHeight = 0;
        verticalSash.setLayout(sashLayout);
        verticalSash.setLayoutData(new GridData(GridData.FILL_BOTH));

        Composite historyGroup = new Composite(verticalSash, SWT.NULL);
        GridLayout historyLayout = new GridLayout();
        historyLayout.verticalSpacing = 5;
        historyLayout.marginHeight = 0;
        historyGroup.setLayout(historyLayout);
        historyGroup.setLayoutData(new GridData(GridData.FILL_BOTH));
        
        Composite filterGroup = new Composite(historyGroup, SWT.NULL);
        GridLayout filterLayout = new GridLayout();
        filterLayout.verticalSpacing = 0;
        filterLayout.marginHeight = 0;
        filterLayout.numColumns = 3;
        filterGroup.setLayout(filterLayout);
        filterGroup.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL));
        
        Label filterLabel = new Label(filterGroup, SWT.NULL);
        filterLabel.setText(Messages.MergeWizardRevisionsPage_filter);
        filterText = new Text(filterGroup, SWT.BORDER);
        filterText.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL));
        filterText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				tableHistoryViewer.addFilter(new TextViewFilter());
				tableHistoryViewer.refresh();
				TableItem[] items = tableHistoryViewer.getTable().getItems();
				for (TableItem item : items) {
					ILogEntry entry = adaptToLogEntry(item.getData());
					if (entry != null) {
						SVNRevision.Number revision = entry.getRevision();
						item.setChecked(selectedRevisions.contains(revision));
					}
				}
				showMessage();
			}
		});

		this.moreOptionsButton = new Button(filterGroup, 8);
		this.moreOptionsButton
				.setText(Messages.MergeWizardRevisionsPage_moreOptions);
		SelectionListener selectionListener = getSelectionListener();
		this.moreOptionsButton.addSelectionListener(selectionListener);

		historyTableProvider = new HistoryTableProvider(SWT.BORDER
				| SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION
				| SWT.CHECK, pageName);
		historyTableProvider.setIncludeMergeRevisions(false);
		historyTableProvider.setIncludeTags(false);
		tableHistoryViewer = historyTableProvider.createTable(historyGroup);
		data = new GridData(GridData.FILL_BOTH);
		data.widthHint = 500;
		data.heightHint = 100;
		tableHistoryViewer.getTable().setLayoutData(data);

		tableHistoryViewer.setContentProvider(new IStructuredContentProvider() {
			public void dispose() {
			}

			public void inputChanged(Viewer viewer, Object oldInput,
					Object newInput) {
			}

			public Object[] getElements(Object inputElement) {
				if (entries == null)
					return new ILogEntry[0];
				return entries;
			}
		});

		tableHistoryViewer.setInput(getUrl());
		tableHistoryViewer
				.addSelectionChangedListener(new ISelectionChangedListener() {
					public void selectionChanged(SelectionChangedEvent event) {
						generateChangeLogButton.setEnabled(!tableHistoryViewer
								.getSelection().isEmpty());
						TableItem[] items = tableHistoryViewer.getTable()
								.getItems();
						for (TableItem item : items) {
							ILogEntry entry = adaptToLogEntry(item.getData());
							SVNRevision.Number revision = entry.getRevision();
							if (item.getChecked()) {
								selectedRevisions.add(revision);
							} else {
								selectedRevisions.remove(revision);
							}
						}
						if (selectedRevisions.size() == 0) {
							if (message == null)
								showMessage();
							else
								setMessage(message);
						} else {
							// check size of entries and table data

							setMessage(selectedRevisions.size()
									+ Messages.MergeWizardRevisionsPage_revisionsSelected);
						}
						setPageComplete(canFinish());
					}
				});
		tableHistoryViewer
				.addSelectionChangedListener(new ISelectionChangedListener() {
					public void selectionChanged(SelectionChangedEvent event) {
						setPageComplete(canFinish());
						ISelection selection = event.getSelection();
						if (selection == null
								|| !(selection instanceof IStructuredSelection)) {
							textViewer.setDocument(new Document("")); //$NON-NLS-1$
							changePathsViewer.setInput(null);
							return;
						}
						IStructuredSelection ss = (IStructuredSelection) selection;
						if (ss.size() != 1) {
							textViewer.setDocument(new Document("")); //$NON-NLS-1$
							changePathsViewer.setInput(null);
							return;
						}
						LogEntry entry = (LogEntry) ss.getFirstElement();
						textViewer
								.setDocument(new Document(entry.getComment()));
						changePathsViewer.setCurrentLogEntry(entry);
						changePathsViewer.setInput(entry);
					}
				});

		generateChangeLogAction = new GenerateChangeLogAction(
				new ISelectionProvider() {
					public void addSelectionChangedListener(
							ISelectionChangedListener listener) {
					}

					public ISelection getSelection() {
						return tableHistoryViewer.getSelection();
					}

					public void setSelection(ISelection selection) {
					}

					public void removeSelectionChangedListener(
							ISelectionChangedListener listener) {
					}
				});

		MenuManager menuMgr = new MenuManager();
		Menu menu = menuMgr.createContextMenu(tableHistoryViewer.getTable());
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager menuMgr) {
				if (!tableHistoryViewer.getSelection().isEmpty()) {
					menuMgr.add(new ToggleSelectionAction());
					menuMgr.add(generateChangeLogAction);
				}
			}
		});
		menuMgr.setRemoveAllWhenShown(true);
		tableHistoryViewer.getTable().setMenu(menu);

		Composite commentGroup = new Composite(verticalSash, SWT.NULL);
		GridLayout commentLayout = new GridLayout();
		commentLayout.verticalSpacing = 0;
		commentLayout.marginHeight = 0;
		commentGroup.setLayout(commentLayout);
		commentGroup.setLayoutData(new GridData(GridData.FILL_BOTH));

		textViewer = new TextViewer(commentGroup, SWT.H_SCROLL | SWT.V_SCROLL
				| SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.READ_ONLY);
		data = new GridData(GridData.FILL_BOTH);
		data.heightHint = 100;
		data.widthHint = 500;
		textViewer.getControl().setLayoutData(data);

		Composite pathGroup = new Composite(verticalSash, SWT.NULL);
		GridLayout pathLayout = new GridLayout();
		pathLayout.verticalSpacing = 0;
		pathLayout.marginHeight = 0;
		pathGroup.setLayout(pathLayout);
		pathGroup.setLayoutData(new GridData(GridData.FILL_BOTH));

		ViewForm viewerPane = new ViewForm(pathGroup, SWT.BORDER | SWT.FLAT);
		viewerPane.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true,
				1, 1));
		CLabel toolbarLabel = new CLabel(viewerPane, SWT.NONE) {
			public Point computeSize(int wHint, int hHint, boolean changed) {
				return super.computeSize(wHint, Math.max(24, hHint), changed);
			}
		};
		toolbarLabel.setText(Messages.MergeWizardRevisionsPage_2);
		viewerPane.setTopLeft(toolbarLabel);
		ToolBar toolbar = new ToolBar(viewerPane, SWT.FLAT);
		viewerPane.setTopCenter(toolbar);
		ToolBarManager toolbarManager = new ToolBarManager(toolbar);
		
		toolbarManager.add(new Separator());
		toolbarManager.add(new ControlContribution("showCompare") {			 //$NON-NLS-1$
			@Override
			protected Control createControl(Composite parent) {
          	  showCompareButton = new Button(parent, SWT.TOGGLE | SWT.FLAT);
          	  showCompareButton.setImage(SVNUIPlugin.getImage(ISVNUIConstants.IMG_SYNCPANE));
          	  showCompareButton.setToolTipText(Messages.MergeWizardRevisionsPage_4);
          	  showCompareButton.setSelection(showCompare);
          	  showCompareButton.addSelectionListener(new SelectionAdapter() {				
				public void widgetSelected(SelectionEvent e) {
					showComparePane(!showCompare);
					if (showCompare) {
						compareRevisions();
					}
				}
          	  });
			  return showCompareButton;
			}
		});
		
		toolbarManager.update(true);
		
        ChangePathsTreeContentProvider contentProvider = new ChangePathsTreeContentProvider();
        changePathsViewer = new ChangePathsTreeViewer(viewerPane, contentProvider);
        
        viewerPane.setContent(changePathsViewer.getTree());
        
        changePathsViewer.addDoubleClickListener(new IDoubleClickListener() {			
			public void doubleClick(DoubleClickEvent event) {
				compareRevisions();
			}
		});
        
        changePathsViewer.getTree().addSelectionListener(new SelectionAdapter() {		
			public void widgetSelected(SelectionEvent e) {
				if (showCompare) {
					compareRevisions();
				}
			}
		});
		
		setPageComplete(canFinish());
		
		if (message == null) setMessage(Messages.MergeWizardRevisionsPage_specifyRevisions);
		else setMessage(message);
		
		try {
			int[] weights = new int[3];
			weights[0] = settings.getInt("MergeWizardRevisionsPageWeights0"); //$NON-NLS-1$
			weights[1] = settings.getInt("MergeWizardRevisionsPageWeights1"); //$NON-NLS-1$
			weights[2] = settings.getInt("MergeWizardRevisionsPageWeights2"); //$NON-NLS-1$
			verticalSash.setWeights(weights);
		} catch (Exception e) {}
		
		compareViewerPane = new CompareViewerSwitchingPane(horizontalSash, SWT.BORDER | SWT.FLAT) {
			protected Viewer getViewer(Viewer oldViewer, Object input) {
				CompareConfiguration cc = compareInput.getCompareConfiguration();
				cc.setLeftEditable(false);
				cc.setRightEditable(false);
				cc.setLeftLabel(compareInput.getLeftLabel());
				cc.setRightLabel(compareInput.getRightLabel());
				return CompareUI.findContentViewer(oldViewer, input, this, cc);	
				}
			};
	    compareViewerPane.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
	    
	    try {
			int[] weights = new int[2];
			weights[0] = settings.getInt("MergeWizardRevisionsPageWeightsHorizontal0"); //$NON-NLS-1$
			weights[1] = settings.getInt("MergeWizardRevisionsPageWeightsHorizontal1"); //$NON-NLS-1$
			horizontalSash.setWeights(weights);
	    } catch (Exception e) {}
		
		Composite buttonGroup = new Composite(composite, SWT.NULL);
		GridLayout buttonLayout = new GridLayout();
		buttonLayout.numColumns = 3;
		buttonGroup.setLayout(buttonLayout);
		data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
		buttonGroup.setLayoutData(data);	
		
		generateChangeLogButton = new Button(buttonGroup, SWT.PUSH);
		generateChangeLogButton.setText(Messages.MergeWizardRevisionsPage_generateChangeLog);
		generateChangeLogButton.setEnabled(false);
		generateChangeLogButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				generateChangeLogAction.run();
			}
		});
		
		if (!showCompare) {
			horizontalSash.setMaximizedControl(verticalSash);
		} else {
			showCompareButton.setSelection(true);
		}
		
		setControl(composite);		
	}

	private SelectionListener getSelectionListener() {
		SelectionListener selectionListener = new SelectionAdapter() {

			public void widgetSelected(SelectionEvent e) {

				if (e.getSource() == MergeWizardRevisionsPage.this.moreOptionsButton) {

					if (dialog == null) {
						final TreeSet<String> set = new TreeSet<String>();
						if (entries != null && entries.length > 0) {
							set.add(ALL_AUTHORS);
							for (int i = 0; i < entries.length; i++) {
								if (entries[i].getAuthor() != null) {
									set.add(entries[i].getAuthor());
								}
							}
							if (set.size() == 2) {
								set.remove(ALL_AUTHORS);
							}
						}
						dialog = new FilterRevisionsDialog(getShell(), set);
					}
					dialog
							.setRemoteResource(MergeWizardRevisionsPage.this.remoteResource);
					int returnCode = dialog.open();
					if (returnCode < 1) {
						clearSearchCriteria();
						historySearchFilter = new HistorySearchViewerFilter(
								dialog.getUser(), dialog.getComment(), dialog
										.getStartDate(), dialog.getEndDate(),
								dialog.getRegExp(), dialog.getStartRevision(),
								dialog.getEndRevision());
						tableHistoryViewer
								.setFilters(new ViewerFilter[] { historySearchFilter });
						MergeWizardRevisionsPage.this.tableHistoryViewer
								.refresh();

						TableItem[] items = tableHistoryViewer.getTable()
								.getItems();
						for (TableItem item : items) {
							ILogEntry entry = adaptToLogEntry(item.getData());
							if (entry != null) {
								historySearchFilter.select(tableHistoryViewer,
										null, entry);
							}
						}
						showMessage();
						setPageComplete(canFinish());
					}
				}
			}

			private void clearSearchCriteria() {
				filterText.setText("");
			}
		};
		return selectionListener;
	}

	protected void showMessage() {
		TableItem[] items = tableHistoryViewer.getTable().getItems();
		if ((entries != null && items != null)
				&& (entries.length != items.length)) {
			setMessage(Messages.MergeWizardRevisionsPage_specifyRevisions
					.concat(" "
							+ Messages.MergeWizardRevisionsPage_filteredList));
		} else {
			setMessage(Messages.MergeWizardRevisionsPage_specifyRevisions);
		}
	}

	protected ILogEntry adaptToLogEntry(Object element) {
		ILogEntry entry = null;
		if (element instanceof ILogEntry) {
			entry = (ILogEntry) element;
		} else if (element instanceof IAdaptable) {
			entry = (ILogEntry)((IAdaptable)element).getAdapter(ILogEntry.class);
		}
		return entry;
	}

	public boolean canFinish() {
		return getSelectedRevisions().length > 0;
	}

	@Override
	public void setVisible(boolean visible) {
		super.setVisible(visible);
		if (visible) {
			if (((MergeWizard)getWizard()).isRetrieveRevisionsMethodChanged() || fromUrl == null || !fromUrl.equals(standardPage.getFromUrl())) refresh();
			((MergeWizard)getWizard()).setRetrieveRevisionsMethodChanged(false);
			// This is a hack to get around an initial sorting problem on OSx.
			if (!pageShown) {
				pageShown = true;
				historyTableProvider.setSortColumn(tableHistoryViewer, 0);
				historyTableProvider.setSortColumn(tableHistoryViewer, 0);
			}
		}
	}
	
	private void setCompareInput(final SVNCompareEditorInput input, boolean run) throws InterruptedException, InvocationTargetException {
		if (run) {
			input.run(new NullProgressMonitor());
		}
		compareViewerPane.setInput(null);
		compareViewerPane.setInput(input.getCompareResult());
    }
    
	public void showComparePane(boolean showCompare) {
		this.showCompare = showCompare;
		if (showCompare) {
			horizontalSash.setMaximizedControl(null);
		} else {
			horizontalSash.setMaximizedControl(verticalSash);
		}
		
	}
	
	private void refresh() {
		IRunnableWithProgress runnable = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
				try {
					monitor.setTaskName(Messages.MergeWizardRevisionsPage_retrievingRemoteResource);
					monitor.beginTask(Messages.MergeWizardRevisionsPage_retrievingRemoteResource, IProgressMonitor.UNKNOWN);
					monitor.worked(1);
					remoteResource = repositoryLocation.getRemoteFile(new SVNUrl(getUrl()));
					monitor.done();
				} catch (Exception e) {
					Activator.handleError(e);
				}		
			}			
		};		
		setPageComplete(false);
		setErrorMessage(null);
		fromUrl = standardPage.getFromUrl();
		remoteResource = null;
		try {
			getContainer().run(true, false, runnable);
			if (remoteResource == null) {
				setErrorMessage(Messages.MergeWizardRevisionsPage_errorRetrievingRemoteResource);
				entries = new ILogEntry[0];
			}
			else {
				getLogEntries(false, false);
				if (noEligibleEntries) {
					setErrorMessage(Messages.MergeWizardRevisionsPage_noEligibleRevisions);
				}
			}
		} catch (Exception e) {
			Activator.handleError(e);
			setErrorMessage(e.getMessage());
			entries = new ILogEntry[0];
		}		
		if (tableHistoryViewer.getInput() == null) tableHistoryViewer.setInput(getUrl());
		else tableHistoryViewer.refresh();
	}
	
	private void getLogEntries(final boolean getAll, final boolean getNext) {
		IRunnableWithProgress runnable = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
            	try {
    				monitor.setTaskName(Messages.MergeWizardRevisionsPage_retrievingRevisionLogInfo);
                	monitor.beginTask(Messages.MergeWizardRevisionsPage_retrievingRevisionLogInfo, 5);				            		
	            	if (SVNUIPlugin.getPlugin().getPreferenceStore().getBoolean(ISVNUIConstants.PREF_SHOW_TAGS_IN_REMOTE))
	            		tagManager = new AliasManager(remoteResource.getUrl());
					monitor.worked(1);
					noEligibleEntries = false;
					entries = getEligibleLogEntries(monitor);
					monitor.worked(1);
					if (entries == null || entries.length == 0) noEligibleEntries = true;
					monitor.worked(1);
            	} catch (Exception e) {
    				Activator.handleError(e);
					Display.getDefault().asyncExec(new Runnable() {						
						public void run() {
							setErrorMessage(Messages.MergeWizardChangeSetRevisionsPage_errorRetrievingLogEntries);
						}
					});
					entries = new ILogEntry[0];            		
            	}
				monitor.worked(1);
				monitor.done();            	
			}		
		};
		
		   try {
			   getContainer().run(true, false, runnable);
			} catch (Exception e1) {
				Activator.handleError(e1);
			} 
	}
	
	@Override
	public boolean isPageComplete() {
		if (!standardPage.selectRevisions()) return true;
		return super.isPageComplete();
	}

	protected ILogEntry[] getEligibleLogEntries(IProgressMonitor monitor) throws TeamException {
		GetEligibleRevisionsCommand logCmd = new GetEligibleRevisionsCommand();
		logCmd.run(monitor);
		return logCmd.getLogEntries();
	}
	
	private SVNRevision.Number[] getAllRevisions() {
		SVNRevision.Number[] entryArray = new SVNRevision.Number[entries.length];
		for (int i = 0; i < entries.length; i++) {
			entryArray[i] = entries[i].getRevision();
		}
		return entryArray;
	}
	
	private SVNRevision.Number[] getSelectedRevisions() {
		SVNRevision.Number[] entryArray = new SVNRevision.Number[selectedRevisions.size()];
		selectedRevisions.toArray(entryArray);
		return entryArray;
	}
	
	public  ILogEntry[] getSelectedLogEntries()
	{
		ArrayList<ILogEntry> selectedEntries = new ArrayList<ILogEntry>();
		TableItem[] items = tableHistoryViewer.getTable().getItems();
		for (int i = 0; i < items.length; i++) {
			if (items[i].getChecked()) {
				ILogEntry entry = (ILogEntry)items[i].getData();
				selectedEntries.add(entry);
			}
		}
		ILogEntry[] entryArray = new ILogEntry[selectedEntries.size()];
		selectedEntries.toArray(entryArray);
		return entryArray;
	}
	
	public SVNRevisionRange[] getRevisions() {
		return SVNRevisionRange.getRevisions(getSelectedRevisions(), getAllRevisions());
	}
	
	public Map<SVNRevision.Number,List<IResource>> getRevisionToResource() {
		return revisionToResource;
	}

	@Override
	public void dispose() {
		if (pageShown) {
	        int[] weights = verticalSash.getWeights();
	        for (int i = 0; i < weights.length; i++) {
	        	settings.put("MergeWizardRevisionsPageWeights" + i, weights[i]); //$NON-NLS-1$ 
	        }
	        weights = horizontalSash.getWeights();
	        for (int i = 0; i < weights.length; i++) {
	        	settings.put("MergeWizardRevisionsPageWeightsHorizontal" + i, weights[i]); //$NON-NLS-1$ 
	        }
		}
        super.dispose();
	}
	
	private String getUrl() {
		if (fromUrl != null) {
			try {
				SVNUrlWithPegRevision svnUrlWithPegRevision = new SVNUrlWithPegRevision(new SVNUrl(fromUrl));
				SVNUrl svnUrl = svnUrlWithPegRevision.getUrl();
				if (svnUrl != null) return svnUrl.toString();
			} catch (MalformedURLException e) {}
		}
		return fromUrl;
	}

	private void compareRevisions() {
		IStructuredSelection sel = (IStructuredSelection)changePathsViewer.getSelection();
		Object sel0 = sel.getFirstElement();
		if (sel0 instanceof LogEntryChangePath) {
			LogEntryChangePath logEntryChangePath = (LogEntryChangePath)sel0;
			try {
				if (!logEntryChangePath.getRemoteResource().isContainer()) {
					ISVNRemoteResource left = logEntryChangePath.getRemoteResource();
					compareInput = compareInputMap.get(left.getUrl().toString() + left.getRevision());
					boolean run = compareInput == null;
					if (compareInput == null) {
						SVNRevision.Number selectedRevision = (SVNRevision.Number)left.getRevision();
						SVNRevision.Number previousRevision = new SVNRevision.Number(selectedRevision.getNumber() - 1);
						ISVNRemoteResource right = new RemoteFile(left.getRepository(), left.getUrl(), previousRevision);
						compareInput = new SVNCompareEditorInput(new ResourceEditionNode(left), new ResourceEditionNode(right));
						compareInputMap.put(left.getUrl().toString() + left.getRevision(), compareInput);
					}
					setCompareInput(compareInput, run);
					showComparePane(true);						
				}
			} catch (Exception e) {
				MessageDialog.openError(getShell(), Messages.MergeWizardRevisionsPage_5, e.getMessage());
			}
		}
	}

	private class ToggleSelectionAction extends Action {

		public ToggleSelectionAction() {
			super();
			setText("Toggle selection"); //$NON-NLS-1$ 		
		}

		@Override
		public void run() {
			TableItem[] items = tableHistoryViewer.getTable().getSelection();
			for (int i = 0; i < items.length; i++) {
				items[i].setChecked(!items[i].getChecked());
				ILogEntry entry = adaptToLogEntry(items[i].getData());
				if (entry != null) {
					SVNRevision.Number revision = entry.getRevision();
					if (items[i].getChecked()) {
						selectedRevisions.add(revision);
					}
					else {
						selectedRevisions.remove(revision);
					}
				}
			}
			setPageComplete(canFinish());
		}

	}
	
    static class ChangePathsTreeContentProvider implements ITreeContentProvider {

        ChangePathsTreeContentProvider() {
        }

        public Object[] getChildren(Object parentElement) {
          if(parentElement instanceof HistoryFolder) {
            return ((HistoryFolder) parentElement).getChildren();
          }
          return null;
        }

        public Object getParent(Object element) {
          return null;
        }

        public boolean hasChildren(Object element) {
          if(element instanceof HistoryFolder) {
            HistoryFolder folder = (HistoryFolder) element;
            return folder.getChildren().length > 0;
          }
          return false;
        }

        public Object[] getElements(Object inputElement) {
          ILogEntry logEntry = (ILogEntry) inputElement;
          return getGroups(logEntry.getLogEntryChangePaths());
        }

		private Object[] getGroups(LogEntryChangePath[] changePaths) {
          // 1st pass. Collect folder names
          Set<String> folderNames = new HashSet<String>();
          for(int i = 0; i < changePaths.length; i++) {
            folderNames.add(getFolderName(changePaths[ i]));
          }

          // 2nd pass. Sorting out explicitly changed folders
          TreeMap<String,HistoryFolder> folders = new TreeMap<String,HistoryFolder>();
          for(int i = 0; i < changePaths.length; i++) {
            LogEntryChangePath changePath = changePaths[ i];
            String path = changePath.getPath();
            if(folderNames.contains(path)) {
              // changed folder
              HistoryFolder folder = folders.get(path);
              if(folder == null) {
                folder = new HistoryFolder(changePath);
                folders.put(path, folder);
              }
            } else {
              // changed resource
              path = getFolderName(changePath);
              HistoryFolder folder = folders.get(path);
              if(folder == null) {
                folder = new HistoryFolder(path);
                folders.put(path, folder);
              }
              folder.add(changePath);
            }
          }

          return folders.values().toArray(new Object[folders.size()]);
        }

        private String getFolderName(LogEntryChangePath changePath) {
          String path = changePath.getPath();
          int n = path.lastIndexOf('/');
          return n > -1 ? path.substring(0, n) : path;
        }

        public void dispose() {
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

      }	
    
    class GetEligibleRevisionsCommand implements ISVNCommand {   
    	 private ILogEntry[] logEntries;
    	 
    	public GetEligibleRevisionsCommand() {
    		super();
    	}
    	
		public void run(IProgressMonitor aMonitor) throws SVNException {
	        logEntries = null;
	        IProgressMonitor monitor = Policy.monitorFor(aMonitor);
	        int taskLength;
	        if (settings.getBoolean(MergeWizard.LAST_RETRIEVE_ELIGIBLE_REVISIONS_SEPARATELY)) {
	        	taskLength = standardPage.getUrls().length;
	        } else {
	        	taskLength = IProgressMonitor.UNKNOWN;
	        }
	        monitor.beginTask(Messages.MergeWizardRevisionsPage_retrievingLogEntries, taskLength);
	        
	        ISVNLogMessage[] logMessages = null;
	        ISVNClientAdapter client = null;
	        try {        	
	        	client = remoteResource.getRepository().getSVNClient();
	        	SVNProviderPlugin.disableConsoleLogging();	        	
	        	if (resources.length > 1 && settings.getBoolean(MergeWizard.LAST_RETRIEVE_ELIGIBLE_REVISIONS_SEPARATELY)) {
	        		logMessages = getCombinedLogMessages(client,monitor);
	        	} else {
		        	String commonRoot = ((MergeWizard)getWizard()).getCommonRoot();
		        	SVNUrl rootUrl = new SVNUrl(commonRoot);
	        		logMessages = client.getMergeinfoLog(ISVNMergeinfoLogKind.eligible, rootUrl, SVNRevision.HEAD, remoteResource.getUrl(), SVNRevision.HEAD, !SVNProviderPlugin.getPlugin().getSVNClientManager().isFetchChangePathOnDemand());
	        	}      
	        	if (remoteResource.isFolder()) {
	                logEntries = LogEntry.createLogEntriesFrom((ISVNRemoteFolder) remoteResource, logMessages, getTags(logMessages));   
	            } else {
	            	logEntries = LogEntry.createLogEntriesFrom((ISVNRemoteFile) remoteResource, logMessages, getTags(logMessages), getUrls(logMessages));
	            }	
	        } catch (Exception e) {
	            throw SVNException.wrapException(e);
	        } finally {
	        	SVNProviderPlugin.enableConsoleLogging();
	        	remoteResource.getRepository().returnSVNClient(client);
	        	monitor.done();
	        }
		}
		
		private ISVNLogMessage[] getCombinedLogMessages(ISVNClientAdapter client, IProgressMonitor monitor) throws Exception {
			List<SVNRevision.Number> revisions = new ArrayList<SVNRevision.Number>();
			List<ISVNLogMessage> logMessages = new ArrayList<ISVNLogMessage>();
			SVNUrl[] urls = standardPage.getUrls();
			for (int i = 0; i < resources.length; i++) {
				IResource resource = resources[i];
				monitor.subTask(resource.getName());
				SVNUrl remoteUrl = urls[i];
				ISVNLocalResource svnResource = SVNWorkspaceRoot.getSVNResourceFor(resource);
				SVNUrl url = svnResource.getUrl();
				ISVNLogMessage[] resourceLogMessages = null;
				try {
					resourceLogMessages = client.getMergeinfoLog(ISVNMergeinfoLogKind.eligible, url, SVNRevision.HEAD, remoteUrl, SVNRevision.HEAD, !SVNProviderPlugin.getPlugin().getSVNClientManager().isFetchChangePathOnDemand());
				} catch (Exception e) {
					Activator.handleError(e);
				}
				if (resourceLogMessages != null) {
					for (ISVNLogMessage logMessage : resourceLogMessages) {
						if (!revisions.contains(logMessage.getRevision())) {
							revisions.add(logMessage.getRevision());
							logMessages.add(logMessage);
						}
						List<IResource> lst = revisionToResource.get(logMessage.getRevision());
						if (lst == null)
						{
							lst = new ArrayList<IResource>(resources.length);
							revisionToResource.put(logMessage.getRevision(),lst);
						}
						lst.add(resource);
					}
				}
				monitor.worked(1);
			}
			ISVNLogMessage[] logMessageArray = new ISVNLogMessage[logMessages.size()];
			logMessages.toArray(logMessageArray);
			return logMessageArray;
		}
		
	    private SVNUrl[] fillUrlsWith(SVNUrl[] urls, SVNUrl url) {
	    	for (int i = 0; i < urls.length;i++) {
	    		urls[i] = url;
	        }
	        return urls;
	    }		
		
	    private SVNUrl[] getUrls(ISVNLogMessage[] logMessages) {
	        SVNUrl[] urls = new SVNUrl[logMessages.length];
	        
	        SVNUrl rootRepositoryUrl = remoteResource.getRepository().getRepositoryRoot();
	        if (rootRepositoryUrl == null) {
	        	// don't know the root repository url, we consider that resource has never been moved
	            // and so that the url was always the same
	            return fillUrlsWith(urls, remoteResource.getUrl());
	        }
	        
	        // we identify the logMessage corresponding to the revision
	        // of the remote resource
	        int indexRemote = -1;
	        for (int i = 0; i < logMessages.length;i++) {
	            if (logMessages[i].getRevision().equals(remoteResource.getLastChangedRevision())) {
	                indexRemote = i;
	            	break;
	            }
	        }
	        if(indexRemote > -1) {
	            urls[indexRemote] = remoteResource.getUrl();
	        }
	        
	            // we get the url of more recent revisions
	            SVNUrl currentUrl = remoteResource.getUrl();
	            for (int i = indexRemote+1; i < logMessages.length;i++) {
	                ISVNLogMessageChangePath[] changePaths = logMessages[i].getChangedPaths();
	                for (int j = 0; j < changePaths.length;j++) {
	                	SVNUrl urlChangedPath = rootRepositoryUrl.appendPath(changePaths[j].getPath());
	                    if (currentUrl.equals(urlChangedPath)) {
	                    	urls[i] = currentUrl;
	                        break;
	                    }
	                    if (changePaths[j].getCopySrcPath() != null) {
	                    	SVNUrl urlCopyPath = rootRepositoryUrl.appendPath(changePaths[j].getCopySrcPath());
	                        if (currentUrl.equals(urlCopyPath)) {
	                        	currentUrl = rootRepositoryUrl.appendPath(changePaths[j].getPath());
	                            urls[i] = currentUrl;
	                            break;
	                        }
	                    }
	                }
	                if (urls[i] == null) {
	                	// something went wrong
	                    return fillUrlsWith(urls, remoteResource.getUrl());
	                }
	            }
	            
	            // we get the url of previous revisions
	            currentUrl = remoteResource.getUrl();
	            for (int i = indexRemote-1; i >= 0;i--) {
	                ISVNLogMessageChangePath[] changePaths = logMessages[i].getChangedPaths();
	                for (int j = 0; j < changePaths.length;j++) {
	                    SVNUrl urlChangedPath = rootRepositoryUrl.appendPath(changePaths[j].getPath());
	                    if (currentUrl.equals(urlChangedPath)) {
	                        urls[i] = currentUrl;
	    
	                        if (changePaths[j].getCopySrcPath() != null) {
	                            SVNUrl urlCopyPath = rootRepositoryUrl.appendPath(changePaths[j].getCopySrcPath());
	                            currentUrl = urlCopyPath;
	                        }
	                        break;
	                    }
	                }
	                if (urls[i] == null) {
	                    // something went wrong
	                    return fillUrlsWith(urls, remoteResource.getUrl());
	                }
	            }
	        return urls;
	    }

		private Tags[] getTags(ISVNLogMessage[] logMessages) throws NumberFormatException {
			Tags[] tags = new Tags[logMessages.length]; 
	        for (int i = 0; i < logMessages.length;i++) {
	        	if (tagManager != null) {
	        		String rev = logMessages[i].getRevision().toString();
	        		int revNo = Integer.parseInt(rev);
	        		tags[i] = new Tags(tagManager.getTags(revNo));
	        	}
	        }
			return tags;
		}		

		public ILogEntry[] getLogEntries() {
			return logEntries;
		}   	
    }

	class TextViewFilter extends ViewerFilter {
		@Override
		public boolean select(Viewer viewer, Object parentElement,
				Object element) {
			if (filterText.getText().trim().length() > 0
					&& element instanceof LogEntry) {
				ILogEntry entry = adaptToLogEntry(element);
				if (entry != null) {
					String filterString = filterText.getText().trim();
					String revision = entry.getRevision().toString();
					if (revision.contains(filterString)) {
						return true;
					}
					String mergedRevisions = entry.getMergedRevisionsAsString();
					if (mergedRevisions != null
							&& mergedRevisions.contains(filterString)) {
						return true;
					}
					String tags = AliasManager.getAliasesAsString(entry
							.getTags());
					if (tags != null && tags.contains(filterString)) {
						return true;
					}
					Date date = entry.getDate();
					String dateString;
					if (date == null) {
						dateString = ""; //$NON-NLS-1$
					} else {
						dateString = DateFormat.getInstance().format(date);
					}
					if (dateString.contains(filterString)) {
						return true;
					}
					String author;
					if (entry.getAuthor() == null) {
						author = ""; //$NON-NLS-1$
					} else {
						author = entry.getAuthor();
					}
					if (author.contains(filterString)) {
						return true;
					}
					String comment = entry.getComment();
					if (comment != null && comment.contains(filterString)) {
						return true;
					}
					return false;
				}
			}
			return true;
		}
	};

}
